package main

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"flag"
	"io"
	"io/ioutil"
	"log"
	"net"
	"os"
	"path"
	"path/filepath"
	"plugin"
	"strings"
	"time"

	"gitee.com/jason_elva8325/socket-quickstart/config"
	"gitee.com/jason_elva8325/socket-quickstart/protocol"
)

var (
	confFile         = flag.String("conf-file", "", "Host path of TOML format config file. If this param set, other params will be lose efficacy")
	listenHost       = flag.String("listen-host", "0.0.0.0", "TCP listen host")
	tcpPort          = flag.String("tcp-port", "3333", "TCP listen port")
	tcpCaCert        = flag.String("tcp-ca-cert", "", "TCP root cert file")
	tcpCert          = flag.String("tcp-cert", "", "TCP cert file")
	tcpKey           = flag.String("tcp-key", "", "TCP cert key file")
	verifyClientCert = flag.Bool("verify-client-cert", false, "Force verify client certification")
	connTimeout      = flag.Duration("conn-timeout", time.Duration(1)*time.Second, "TCP connect timeout")
	processorDir     = flag.String("processor-dir", "", "Processor files store location")
	resourceDir      = flag.String("resource-dir", "", "directory where resource files locate")              // 不能通过configFile设置，必须通过命令行参数设置
	pieceSize        = flag.Int("piece-size", 512, "piece per file byte size, must be between 256 and 8192") // 不能通过configFile设置，必须通过命令行参数设置
	processers       = make(map[string]func([]byte, *log.Logger, ...interface{}) ([]byte, error))            // ...interface{} 用于传递其它全局变量到 processer
	logger           *log.Logger
)

func init() {
	// 全局日志对象定义
	logger = log.New(os.Stderr, "[file-server] ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
}

func main() {
	flag.Parse()

	var (
		netListen net.Listener
		err       error
	)

	// 生成服务配置对象
	conf := parseConfigure()

	// 请求分片大小的检查
	if *pieceSize < 256 || *pieceSize > 8192 {
		logger.Println("invalid piece per file byte size, must be between 256 and 8192")
		os.Exit(1)
	}

	// 获取processor文件列表
	if conf.Common.ProcessorDir == "" {
		dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
		if err != nil {
			logger.Printf("Processor absolutly file path get fail: %v\n", err)
			os.Exit(1)
		}
		conf.Common.ProcessorDir = dir
	} else {
		dir, err := filepath.Abs(filepath.Dir(conf.Common.ProcessorDir))
		if err != nil {
			logger.Printf("Processor absolutly file path get fail: %v\n", err)
			os.Exit(1)
		}
		conf.Common.ProcessorDir = dir
	}
	rd, err := ioutil.ReadDir(conf.Common.ProcessorDir)
	if err != nil {
		logger.Printf("Read processors from path fail: %v\n", err)
		os.Exit(1)
	}
	// 循环载入所有processor
	var processorCnt = 0
	for _, fi := range rd {
		if !fi.IsDir() && path.Ext(fi.Name()) == ".so" {
			p, err := plugin.Open(conf.Common.ProcessorDir + "/" + fi.Name())
			if err != nil {
				logger.Printf("processor file open fail: %v\n", err)
				os.Exit(1)
			}
			f, err := p.Lookup("ProcessorRegistry")
			if err != nil {
				logger.Printf("processor registry func get fail: %v\n", err)
				os.Exit(1)
			}
			f.(func(map[string]func([]byte, *log.Logger, ...interface{}) ([]byte, error)))(processers)
			processorCnt++
		} else {
			logger.Printf("ignore file in processor path: %s\n", fi.Name())
		}
	}
	logger.Printf("Processor load path: %s, Load processor count: %d\n", conf.Common.ProcessorDir, processorCnt)

	// 判断是否启用SSL
	if conf.Common.TCPCert != "" && conf.Common.TCPKey != "" {
		crt, err := tls.LoadX509KeyPair(conf.Common.TCPCert, conf.Common.TCPKey)
		if err != nil {
			logger.Printf("TCP with SSL Cert load fail: %s\n", err.Error())
			os.Exit(1)
		}
		tlsConfig := &tls.Config{
			MinVersion:   tls.VersionTLS11,
			Certificates: []tls.Certificate{crt},
			Time:         time.Now,
			Rand:         rand.Reader,
		}
		// 判断是否开启客户端证书校验
		if conf.Common.VerifyClientCert == true {
			tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
			// 判断是否载入私有验证(根)证书
			if conf.Common.TCPCACert != "" {
				pool := x509.NewCertPool()
				caData, err := ioutil.ReadFile(conf.Common.TCPCACert)
				if err != nil {
					logger.Printf("SSL Root Cert file load fail: %s\n", err.Error())
					os.Exit(1)
				}
				pool.AppendCertsFromPEM(caData)
				tlsConfig.ClientCAs = pool
			}
		}
		netListen, err = tls.Listen("tcp", conf.Common.ListenHost+":"+conf.Common.TCPPort, tlsConfig)
	} else {
		netListen, err = net.Listen("tcp", conf.Common.ListenHost+":"+conf.Common.TCPPort)
	}
	if err != nil {
		logger.Printf("TCP listen fail: %s\n", err.Error())
		os.Exit(1)
	}
	defer netListen.Close()

	for {
		// 持续接收socket请求内容
		conn, err := netListen.Accept()
		if err != nil {
			logger.Printf("TCP accept fail: %s\n", err)
			continue
		}
		conn.SetReadDeadline(time.Now().Add(conf.Common.ConnTimeout))
		logger.Printf("TCP accept connect from %s\n", conn.RemoteAddr().String())
		// 开启新线程处理请求，默认一次连接只接收一组报文
		go handle(conn, conf)
	}
}

func handle(conn net.Conn, conf *config.ConfigFile) {
	defer conn.Close()

	// 定义接收包的缓存对象
	swapBuffer := make([]byte, 0)
	// 开启对tcp连接的内容读取写入
	bufferRW := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
	// 状态基，`0x00`为无用数据，`0x01`为报文数据
	state := 0x00
	// 报文起止判断位
	flag := make([]byte, 2)

	// 循环接收字节数据
	for {
		receiveByte, err := bufferRW.ReadByte()
		if err != nil {
			if err == io.EOF {
				logger.Printf("client %s is close!\n", conn.RemoteAddr().String())
			}
			// 直接退出goroutine，关闭由defer操作完成
			return
		}
		// 获取判断位
		flag[0] = flag[1]
		flag[1] = receiveByte
		// 判断状态基
		switch state {
		case 0x00:
			if bytes.Equal(flag, protocol.MessageHead) { // 判断协议内容开始
				state = 0x01
				swapBuffer = protocol.MessageHead
			}
			break
		case 0x01:
			swapBuffer = append(swapBuffer, receiveByte)
			if bytes.Equal(flag, protocol.MessageEnd) { // 判断协议内容结束
				state = 0x00
				// 解析数据报文
				message, err := protocol.Unpack(swapBuffer)
				if err != nil {
					logger.Println(err)
					bufferRW.Write(protocol.Pack([]byte("-1"))) // 报文格式解析错误，返回-1
				} else if len(message) < 14 { // 2位报文命令 + 12位客户端 ID
					logger.Println("invalid message content length: there must be 2byte command & 12byte client ID at least")
					bufferRW.Write(protocol.Pack([]byte("-2"))) // 报文消息体最小长度错误，返回-2
				} else {
					// 报文业务处理器调用
					command := string(message[:2])
					processor := processers[command]
					if processor != nil {
						response, err := processor(message, logger, *resourceDir, *pieceSize)
						if err != nil {
							logger.Printf("processor excute fail: %v\n", err)
							bufferRW.Write(protocol.Pack([]byte("-4"))) // 业务处理错误，返回-4
						} else {
							bufferRW.Write(protocol.Pack(response)) // 业务处理成功，返回业务结果
						}
					} else {
						logger.Println("no processor found")
						bufferRW.Write(protocol.Pack([]byte("-3"))) // 没有找到对应业务处理器，返回-3
					}
				}
				bufferRW.Flush()

				// 需要长连接，这里的实现方式是：则在每完成一次请求响应后，不断开连接，重置超时时间.
				// 以保证在下一个超时周期内可以正常完成一次请求响应
				conn.SetReadDeadline(time.Now().Add(conf.Common.ConnTimeout))
			}
			break
		}
	}
}

// 生成服务配置对象
func parseConfigure() *config.ConfigFile {
	// 当配置文件设置不为空时，优先使用配置文件内容
	if strings.Trim(*confFile, " ") != "" {
		return config.ParseConfigFile(*confFile, logger)
	}
	return &config.ConfigFile{
		Common: &config.CommonConfig{
			ListenHost:       *listenHost,
			TCPPort:          *tcpPort,
			TCPCACert:        *tcpCaCert,
			TCPCert:          *tcpCert,
			TCPKey:           *tcpKey,
			VerifyClientCert: *verifyClientCert,
			ConnTimeout:      *connTimeout,
			ProcessorDir:     *processorDir,
		},
	}
}
